﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Messaging;
using System.Xml;
using System.IO;
using System.Xml.Linq;

namespace TeamFoundation.EventBus.Client
{
    /// <summary>
    /// Represents a subscription to a TFS Queue.
    /// </summary>
    public sealed class TfsQueueSubscription : IDisposable
    {
        private MessageQueue _msmq;

        /// <summary>
        /// Used to notify strongly typed <see cref="TfsEventArgs"/>.
        /// </summary>
        /// <typeparam name="T">The type of event.</typeparam>
        public interface ITypedEventSubscription<T>
            where T : class
        {
            /// <summary>
            /// Raised when a well-known event occurs.
            /// </summary>
            event EventHandler<TfsKnownEventArgs<T>> TfsNotification;
        }

        /// <summary>
        /// Used to notify strongly typed <see cref="TfsEventArgs"/>.
        /// </summary>
        private interface ITypedEventNotification : IDisposable
        {
            /// <summary>
            /// Raises the event.
            /// </summary>
            /// <param name="sender">The originator of the event.</param>
            /// <param name="args">The event arguments.</param>
            void Raise(object sender, TfsEventArgs args);
        }

        /// <summary>
        /// Used to notify strongly typed <see cref="TfsEventArgs"/>.
        /// </summary>
        private class TypedEventNotification<T> : ITypedEventNotification, ITypedEventSubscription<T>
            where T : class
        {

            #region ITypedEventNotification Members

            public void Raise(object sender, TfsEventArgs args)
            {
                TfsKnownEventArgs<T> known = new TfsKnownEventArgs<T>(
                    args.EventData, args.IdentityData, args.Event, args.Identity,
                    args.SubscriptionId);
                var tmp = TfsNotification;
                if (tmp != null)
                    tmp(sender, known);
            }

            #endregion

            #region ITypedEventSubscription<T> Members

            public event EventHandler<TfsKnownEventArgs<T>> TfsNotification;

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                TfsNotification = null;
            }

            #endregion
        }

        /// <summary>
        /// Occurs when an event arrives from TFS.
        /// </summary>
        public event EventHandler<TfsEventArgs> TfsNotification;

        private bool _isEnabled;
        /// <summary>
        /// Gets or sets whether the subscription is enabled and listening.
        /// </summary>
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                if (_isEnabled != value)
                {
                    if (value)
                        Start();
                    else
                        Stop();
                    _isEnabled = value;
                }
            }
        }

        private string _pipe;
        private static XmlReaderSettings _readerSettings = new XmlReaderSettings();
        private static XmlNameTable _nameTable = new NameTable();
        private Dictionary<Type, ITypedEventNotification> _subscriptions = new Dictionary<Type, ITypedEventNotification>();

        /// <summary>
        /// Static constructor.
        /// </summary>
        static TfsQueueSubscription()
        {
            _readerSettings.CloseInput = false;
            _readerSettings.IgnoreComments = false;
            _readerSettings.IgnoreProcessingInstructions = false;
            _readerSettings.IgnoreWhitespace = false;
            _readerSettings.NameTable = _nameTable;
        }

        #region ctor
        /// <summary>
        /// Creates a new instance of the <see cref="TfsQueueSubscription"/>
        /// class. The subscription will be created if it does not exist.
        /// </summary>
        /// <param name="name">The name of the subscription.</param>
        internal TfsQueueSubscription(string name)
        {
            _pipe = name;
        }
        #endregion

        #region dtor
        /// <summary>
        /// Destructor.
        /// </summary>
        ~TfsQueueSubscription() { }

        /// <summary>
        /// Disposes of the object and any related resources.
        /// </summary>
        public void Dispose()
        {
            try
            {
                if (_msmq != null)
                {
                    _msmq.Dispose();
                    _msmq = null;
                }

                TfsNotification = null;
                lock (_subscriptions)
                {
                    foreach (ITypedEventNotification item in _subscriptions.Values)
                        item.Dispose();
                    _subscriptions.Clear();
                }
            }
            catch { }

            GC.SuppressFinalize(this);
        }

        #endregion

        /// <summary>
        /// Starts waiting for events.
        /// </summary>
        public void Start()
        {
            lock (this)
            {
                if (_msmq != null)
                    return;

                _msmq = new MessageQueue(_pipe, false, true, QueueAccessMode.Receive);
                _msmq.PeekCompleted += new PeekCompletedEventHandler(PeekCompleted);
                BeginRead();
            }
        }

        /// <summary>
        /// Stops waiting for events.
        /// </summary>
        public void Stop()
        {
            lock (this)
            {
                if (_msmq != null)
                {
                    _msmq.Dispose();
                    _msmq = null;
                }
            }
        }

        private void BeginRead()
        {
            if (_msmq != null)
            {
                _msmq.BeginPeek();
            }
        }

        private void PeekCompleted(object sender, PeekCompletedEventArgs e)
        {
            if (_msmq == null)
                return;

            using (MessageQueueTransaction t = new MessageQueueTransaction())
            {
                t.Begin();
                using (MemoryStream byteData = new MemoryStream())
                {
                    try
                    {
                        lock (_readerSettings)
                        {
                            Message msg = _msmq.Receive(t);
                            using (XmlReader reader = XmlReader.Create(msg.BodyStream, _readerSettings))
                            {
                                XDocument doc = XDocument.Load(reader);
                                XElement binary = doc.Elements("base64Binary").FirstOrDefault();
                                if (binary != null)
                                {
                                    byte[] tfsPayload = Convert.FromBase64String(binary.Value);
                                    byteData.Write(tfsPayload, 0, tfsPayload.Length);
                                    byteData.Seek(0, SeekOrigin.Begin);
                                }
                            }

                            RaiseEvent(byteData);
                            t.Commit();
                            BeginRead();
                        }
                    }
                    catch (Exception ex)
                    {
                        t.Abort();
                        IsEnabled = false;
                        Console.WriteLine(ex.Message);
                        BeginRead();
                    }
                }
            }
        }

        private void RaiseEvent(MemoryStream byteData)
        {
            TfsEventArgs args = TfsEventArgs.Parse(byteData, _readerSettings);
            var tmp = TfsNotification;

            if (tmp != null) // Weak event.
                tmp(this, args); // Prevent exceptions.

            ITypedEventNotification notify;
            if (args.Event != null) // Strong event.
            {
                lock (_subscriptions)
                {
                    if (!_subscriptions.TryGetValue(args.Event.GetType(), out notify))
                        return;
                }

                notify.Raise(this, args);
            }
        }

        /// <summary>
        /// Gets a strong event subscription mechanism.
        /// </summary>
        /// <typeparam name="T">The mechanism.</typeparam>
        /// <returns>The mechanism.</returns>
        public ITypedEventSubscription<T> When<T>()
            where T : class
        {
            ITypedEventNotification res;
            lock (_subscriptions)
            {
                if (!_subscriptions.TryGetValue(typeof(T), out res))
                {
                    res = new TypedEventNotification<T>();
                    _subscriptions.Add(typeof(T), res);
                }
            }
            return (ITypedEventSubscription<T>)res;
        }

    }
}
